package com.example.czy.utils;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.czy.annotation.Query;
import com.example.czy.annotation.QueryOR;
import com.example.czy.base.qo.PageQO;
import com.example.czy.constant.SymbolConstant;
import com.example.czy.exception.BaseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author czy
 * @describe 实体类转 QueryWrapper 构造条件
 * 多个复杂的查询条件，通过注解轻松实现
 * 支持 多表复杂关联查询
 * 支持 常用类型：EQ、LK（LIKE）、LLK（LEFT LIKE）、RLK（RIGHT LIKE）、GT、LT、GE、LE、IN、NIN（NOT IN）、ES（EXISTS）、NES（NOT EXISTS）、BT（BETWEEN）、NBT（NOT BETWEEN）、INL（IS NULL）、NNL（NOT NULL） 等等
 * 支持 排序，关键字：sort - 数组、支持集合、字符串逗号分割  示例：（id DESC,startTime DESC）
 * 支持 AND 和 OR 拼接
 * 支持 多字段 LIKE 适用于根据名称、描述 模糊查询场景
 * 支持 JSON 条件如： [{"name":"lk 张三"},{"id":"in 1,2,3,4,5"}]、[{"name or description":"lk 张三"},{"name":"lk 张,李,王"}]
 * 支持 自定义条件 Map 传输
 * 支持 多表指定具体别名
 * 支持 自定义剔除条件
 * <p>
 * 调用示例：
 * https://blog.csdn.net/jiandanzhengban/article/details/126555921
 */
public class QueryUtils {
    private static String SORTS = "SORTS";

    private static String OR = SqlKeyword.OR.getSqlSegment();

    /**
     * @param qo 查询对象DTO、QO等
     * @return
     */
    public static <T> QueryWrapper<T> buildQueryWrapper(Object qo) {
        QueryWrapper<T> queryWrapper = Wrappers.query();

        try {
            List<Field> fields = fields(qo.getClass(), new ArrayList<>());
            for (Field field : fields) {
                boolean accessible = field.isAccessible();
                field.setAccessible(true);

                // 处理排序字段
                buildSort(queryWrapper, field, qo);

                // 处理 @Query 注解的条件
                buildQuery(field.getAnnotation(Query.class), queryWrapper, field, qo);

                // 处理 @QueryOr 注解的条件
                buildQueryOr(field.getAnnotation(QueryOR.class), queryWrapper, field);

                field.setAccessible(accessible);
            }
        } catch (Exception e) {
            throw new BaseException(400, e.getMessage());
        }
        return queryWrapper;
    }


    /**
     * @param json JSON条件
     * @return
     */
    public static <T> QueryWrapper<T> buildQueryWrapperJson(String json) {
        return buildQueryWrapperJson(getParams(json, null, null, null));
    }

    /**
     * 解析JSON，构建Wrapper查询条件
     *
     * @param json [{"name":"lk 1"}] （name like '%1%'）
     *             多表关联查询，指定别名 Map
     * @return queryWrapper
     */
    public static <T> QueryWrapper<T> buildQueryWrapperJson(String json, QueryAlias alias) {
        return buildQueryWrapperJson(getParams(json, alias, null, null));
    }


    /**
     * 解析JSON，构建Wrapper查询条件
     *
     * @param json       json [{"name":"lk 1"}] （name like '%1%'）
     * @param alias      多表关联查询，指定别名
     * @param paramMap   添加参数 Map<String, String>
     * @param removeKeys 清除参数，多个逗号分割
     * @return
     */
    public static <T> QueryWrapper<T> buildQueryWrapperJson(String json, QueryAlias alias, Map<String, String> paramMap, String removeKeys) {
        return buildQueryWrapperJson(getParams(json, alias, paramMap, removeKeys));
    }

    /**
     * @param paramMap 参数条件
     * @param alias    参数条件别名
     * @return
     */
    public static <T> QueryWrapper<T> buildQueryWrapperJson(Map<String, String> paramMap, QueryAlias alias) {
        return buildQueryWrapperJson(getParams(null, alias, paramMap, null));
    }

    /**
     * @param paramMap   参数条件
     * @param alias      参数条件别名
     * @param removeKeys 移除条件
     * @return
     */
    public static <T> QueryWrapper<T> buildQueryWrapperJson(Map<String, String> paramMap, QueryAlias alias, String removeKeys) {
        return buildQueryWrapperJson(getParams(null, alias, paramMap, removeKeys));
    }


    /**
     * 解析JSON，构建Wrapper查询条件
     *
     * @param paramMap 添加参数 Map<String, String>
     * @return queryWrapper
     */
    public static <T> QueryWrapper<T> buildQueryWrapperJson(Map<String, String> paramMap) {
        // 构建查询条件
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        for (Map.Entry<String, String> paramEntry : paramMap.entrySet()) {
            // 以一个空格作为分割符，默认第一个为条件类型
            // 字段名
            String fieldName = paramEntry.getKey();
            // 条件值
            String value = paramEntry.getValue();
            // 条件集合
            List<String> conditions = Arrays.stream(value.split(" ", 2)).collect(Collectors.toList());
            if (!ObjectUtils.isEmpty(conditions)) {
                // 支持排序 如：[{"sorts":"createTime desc,type asc"}]
                if (SORTS.equalsIgnoreCase(getOriginalField(fieldName))) {
                    buildSort(queryWrapper, objToList(value));
                    continue;
                }
                // 条件类型 ，没有默认为eq
                String op = Arrays.stream(Query.Type.values()).anyMatch(item -> (item.getName().equalsIgnoreCase(conditions.stream().findFirst().get()))) ? conditions.stream().findFirst().get() : Query.Type.EQ.toString();
                // 条件内容
                String condition = conditions.get(conditions.size() - 1);
                // 支持多个字段or 如：[{"name or description":"lk 1314"}]
                List<String> fieldNames = splitOr(fieldName);
                if (fieldNames.size() > 1) {
                    queryWrapper.and(qw -> {
                        // 忽略大小写截取or
                        for (String fieldNameSplit : fieldNames) {
                            buildType(qw, op, fieldNameSplit, condition, true);
                        }
                    });
                } else {
                    // 支持一个字段or 如：[{"name":"lk 张 OR 李"}]
                    if (splitOr(value).size() > 1) {
                        List<String> orConditions = Arrays.stream(value.split(" ")).collect(Collectors.toList());
                        // 移除操作符，保留条件
                        orConditions.removeIf(item -> op.equals(item) || OR.equalsIgnoreCase(item));
                        // 支持 [{"name":"lk 张 OR 李"}]
                        queryWrapper.and(qw -> {
                            for (String orCondition : orConditions) {
                                buildType(qw, op, fieldName, orCondition, true);
                            }
                        });
                    } else {
                        buildType(queryWrapper, op, fieldName, condition, false);
                    }
                }
            }
        }
        return queryWrapper;
    }


    /**
     * 参数设置别名
     *
     * @param paraMap    参数 Map
     * @param queryAlias 多表关联查询，指定别名
     * @return 处理后的 Map
     */
    public static Map<String, String> setAlias(Map<String, String> paraMap, QueryAlias queryAlias) {
        if (ObjectUtils.isEmpty(queryAlias)) {
            return paraMap;
        }

        // 创建一个新的 Map 来存储处理后的键值对，避免修改原映射
        Map<String, String> resultMap = new HashMap<>();

        // 默认别名
        String defaultAlias = queryAlias.getDefaultAlias();
        Map<String, Set<String>> aliasMap = buildAliasMap(queryAlias.getAlias());

        // 遍历原始参数映射
        for (Map.Entry<String, String> entry : paraMap.entrySet()) {
            String originalKey = entry.getKey();
            String value = entry.getValue();
            boolean aliasProcessed = false;

            // 处理别名
            if (aliasMap != null) {
                for (Map.Entry<String, Set<String>> aliasEntry : aliasMap.entrySet()) {
                    List<String> keys = splitOr(originalKey);
                    if (ObjectUtils.isEmpty(keys)) {
                        if (aliasEntry.getValue().contains(originalKey)) {
                            resultMap.put(aliasEntry.getKey() + SymbolConstant.SPOT + originalKey, value);
                            aliasProcessed = true;
                            break;
                        }
                    } else {
                        // 支持or别名 如： [{"id or name":"lk 1"}]
                        for (String key : keys) {
                            if (aliasEntry.getValue().contains(key)) {
                                key = key.replace(key, aliasEntry.getKey() + SymbolConstant.SPOT + key);
                            }
                            resultMap.put(key, value);
                        }
                        aliasProcessed = true;
                        break;
                    }
                }
            }

            if (!aliasProcessed) {
                setDefaultAlias(originalKey, value, defaultAlias, resultMap);
            }
        }
        return resultMap;
    }


    /**
     * 构建别名映射
     *
     * @param aliasMap 原始别名映射
     * @return 处理后的别名映射
     */
    private static Map<String, Set<String>> buildAliasMap(Map<String, String> aliasMap) {
        Map<String, Set<String>> result = new HashMap<>();
        if (aliasMap != null) {
            for (Map.Entry<String, String> entry : aliasMap.entrySet()) {
                String[] aliases = entry.getValue().split(SymbolConstant.COMMA);
                Set<String> aliasSet = new HashSet<>(Arrays.asList(aliases));
                result.put(entry.getKey(), aliasSet);
            }
        }
        return result;
    }

    /**
     * 设置默认别名
     *
     * @param key
     * @param value
     * @param defaultAlias
     * @param resultMap
     */
    private static void setDefaultAlias(String key, String value, String defaultAlias, Map<String, String> resultMap) {
        if (StringUtils.isNotBlank(defaultAlias) && !key.contains(SymbolConstant.SPOT)) {
            if (SORTS.equalsIgnoreCase(key)) {
                buildSortAlias(key, value, defaultAlias, resultMap);
            } else {
                List<String> keys = splitOr(key);
                if (ObjectUtils.isEmpty(keys)) {
                    key = defaultAlias + SymbolConstant.SPOT + key;
                } else {
                    for (String k : keys) {
                        key = key.replace(k, defaultAlias + SymbolConstant.SPOT + k);
                    }
                }
                resultMap.put(key, value);
            }
        } else {
            // 如果别名无效或与原键相同，则保留原键
            resultMap.put(key, value);
        }
    }

    /**
     * 构建sort（排序关键字）别名
     *
     * @param key
     * @param value
     * @param alias
     * @param resultMap
     */
    private static void buildSortAlias(String key, String value, String alias, Map<String, String> resultMap) {
        List<String> orgSorts = objToList(value);
        List<String> sorts = new ArrayList<>(orgSorts.size());
        for (String orgSort : orgSorts) {
            if (orgSort.contains(SymbolConstant.SPOT)) {
                sorts.add(orgSort);
            } else {
                sorts.add(alias + SymbolConstant.SPOT + orgSort);
            }
        }
        resultMap.put(key, sorts.stream().collect(Collectors.joining(SymbolConstant.COMMA)));
    }

    /**
     * json转map
     *
     * @param json
     * @return map
     */
    public static Map<String, String> getParams(String json) {
        Map<String, String> resultMap = new LinkedHashMap<>();
        if (!ObjectUtils.isEmpty(json)) {
            List<Map<String, String>> list;
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                Object obj = objectMapper.readValue(json, Object.class);
                if (obj instanceof Collection) {
                    list = (List<Map<String, String>>) obj;
                } else {
                    list = new ArrayList<>();
                    Map<String, String> map = objectMapper.convertValue(obj, Map.class);
                    list.add(map);
                }
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
            for (Map<String, String> map : list) {
                // 如果map中有重复的键，则后面的值会覆盖前面的值
                resultMap.putAll(map);
            }
        }
        return resultMap;
    }

    /**
     * 根据key获取值，不含运算符
     *
     * @param json
     * @param key
     * @return
     */
    public static String getValueByKey(String json, String key) {
        Map<String, String> resultMap = getParams(json);
        String param = resultMap.get(key);
        if (Objects.nonNull(param)) {
            String[] paramSplit = param.split(" ", 2);
            return paramSplit[paramSplit.length - 1];
        }
        return null;
    }


    /**
     * 解析JSON获取参数集合
     *
     * @param json       例如：{"name":"lk 张三"}、[{"name":"lk 张三"},{"id":"1"}]
     * @param alias      多表关联查询，指定别名
     * @param removeKeys 清除参数，多个逗号分割
     * @return List<Map>
     */
    public static Map<String, String> getParams(String json, QueryAlias alias, Map<String, String> paramMap, String removeKeys) {
        Map<String, String> resultMap = getParams(json);
        if (!ObjectUtils.isEmpty(paramMap)) {
            resultMap.putAll(paramMap);
        }
        return setAlias(removeParams(resultMap, removeKeys), alias);
    }


    /**
     * 清除参数
     *
     * @param paramMap 参数 Map
     * @param keys     多个逗号分割
     * @return
     */
    public static Map<String, String> removeParams(Map<String, String> paramMap, String keys) {
        if (StringUtils.isBlank(keys)) {
            return paramMap;
        }
        List<String> keyList = Arrays.asList(keys.split(SymbolConstant.COMMA));
        return paramMap.entrySet().stream().filter(entry -> {
            if (entry.getKey().contains(SymbolConstant.SPOT)) {
                String[] keySplit = entry.getKey().split("\\" + SymbolConstant.SPOT);
                return !keyList.contains(keySplit[keySplit.length - 1]);
            } else {
                return !keyList.contains(entry.getKey());
            }
        }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    /**
     * 处理排序字段
     *
     * @param queryWrapper 条件
     * @param field        字段
     * @param qo           qo查询类
     * @throws IllegalAccessException
     */
    private static <T> void buildSort(QueryWrapper<T> queryWrapper, Field field, Object qo) {
        if (SORTS.equalsIgnoreCase(getOriginalField(field.getName()))) {
            List sorts = null;
            try {
                sorts = objToList(field.get(qo));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            buildSort(queryWrapper, sorts);
        }
    }


    /**
     * 处理排序字段
     *
     * @param queryWrapper 条件
     * @param sorts        排序内容
     */
    private static <T> void buildSort(QueryWrapper<T> queryWrapper, List<String> sorts) {
        if (CollectionUtils.isNotEmpty(sorts)) {
            for (String sort : sorts) {
                if (sort.toUpperCase().contains(SqlKeyword.DESC.name())) {
                    queryWrapper.orderByDesc(StringUtils.camelToUnderline(sort.replaceAll("(?i)" + SqlKeyword.DESC.name(), "")));
                } else {
                    queryWrapper.orderByAsc(StringUtils.camelToUnderline(sort));
                }
            }
        }
    }

    /**
     * 处理 @Query注解的条件，适用于字段 AND条件拼接 如：AND A = 1 AND B = 2
     *
     * @param condition
     * @param queryWrapper
     * @param field
     * @param qo
     * @throws IllegalAccessException
     */
    private static <T> void buildQuery(Query condition, QueryWrapper<T> queryWrapper, Field field, Object qo) {
        if (Objects.nonNull(condition)) {
            // 数据库字段
            String fieldName = condition.field();
            // 多字段like
            String likesFields = condition.likes();
            // 如果未指定则默认当前字段名为数据库字段
            String name = StringUtils.isNotBlank(fieldName) ? fieldName : field.getName();
            // 内容
            Object value = null;
            try {
                value = field.get(qo);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            // 多字段like分割拼接
            if (StringUtils.isNotBlank(likesFields)) {
                String[] likesFieldArray = likesFields.split(SymbolConstant.COMMA);
                Object finalValue = value;
                if (Objects.nonNull(value)) {
                    queryWrapper.and(qw -> {
                        for (String likesField : likesFieldArray) {
                            qw.like(StringUtils.camelToUnderline(likesField), finalValue).or();
                        }
                    });
                }
            } else {
                buildType(queryWrapper, condition.type(), name, value, null);
            }
        }
    }

    /**
     * 处理 @QueryOr注解的条件，适用于类 OR条件拼接 如：a = 1 or b = 1 or ...
     *
     * @param orCondition
     * @param queryWrapper
     * @param field
     */
    private static <T> void buildQueryOr(QueryOR orCondition, QueryWrapper<T> queryWrapper, Field field) {
        if (Objects.nonNull(orCondition)) {
            List<Field> childFields = fields(field.getType(), new ArrayList<>());
            if (CollectionUtils.isNotEmpty(childFields)) {
                queryWrapper.and(qw -> {
                    for (Field childField : childFields) {
                        childField.setAccessible(true);
                        Query childCondition = childField.getAnnotation(Query.class);
                        // 数据库字段
                        String fieldName = childField.getName();
                        // 如果未指定则默认当前字段名为数据库字段
                        String name = StringUtils.camelToUnderline(StringUtils.isNotBlank(fieldName) ? fieldName : field.getName());
                        Object value = null;
                        try {
                            value = childField.get(field.getType().newInstance());
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        }
                        buildType(qw, childCondition.type(), name, value, true);
                    }
                });
            }
        }
    }

    /**
     * 构建条件
     *
     * @param queryWrapper
     * @param op
     * @param fieldName
     * @param value
     * @param isOr
     */
    private static <T> void buildType(QueryWrapper<T> queryWrapper, String op, String fieldName, Object value, Boolean isOr) {
        Query.Type type = Query.Type.get(op.toUpperCase());
        if (Objects.isNull(type)) {
            throw new BaseException(400, "the operator '" + op + "' for param '" + getOriginalField(fieldName) + "' is not supported");
        }
        buildType(queryWrapper, type, fieldName, value, isOr);
    }

    /**
     * 构建条件
     *
     * @param queryWrapper
     * @param type
     * @param name
     * @param value
     * @param isOr
     */
    private static <T> void buildType(QueryWrapper<T> queryWrapper, Query.Type type, String name, Object value, Boolean isOr) {
        name = StringUtils.camelToUnderline(name);
        if (Objects.nonNull(value) || (Query.Type.IS_NULL.equals(type) || Query.Type.NOT_NULL.equals(type))) {
            // 匹配查询类型
            List list;
            switch (type) {
                case EQ:
                case EQ_SYMBOL:
                    queryWrapper.eq(name, value);
                    break;
                case GT:
                case GT_SYMBOL:
                    queryWrapper.gt(name, value);
                    break;
                case LT:
                case LT_SYMBOL:
                    queryWrapper.lt(name, value);
                    break;
                case GE:
                case GE_SYMBOL:
                case GE_SYMBOL_SPLIT:
                    queryWrapper.ge(name, value);
                    break;
                case LE:
                case LE_SYMBOL:
                case LE_SYMBOL_SPLIT:
                    queryWrapper.le(name, value);
                    break;
                case LIKE:
                case LK:
                    queryWrapper.like(name, value);
                    break;
                case LEFT_LIKE:
                case LLK:
                    queryWrapper.likeLeft(name, value);
                    break;
                case RIGHT_LIKE:
                case RLK:
                    queryWrapper.likeRight(name, value);
                    break;
                case NOT_LIKE:
                case NLK:
                    queryWrapper.notLike(name, value);
                    break;
                case IN:
                    list = objToList(value);
                    queryWrapper.in(name, list);
                    break;
                case NOT_IN:
                case NIN:
                    list = objToList(value);
                    queryWrapper.notIn(name, list);
                    break;
                case EXISTS:
                case ES:
                    list = objToList(value);
                    queryWrapper.exists(name, list);
                    break;
                case NOT_EXISTS:
                case NES:
                    list = objToList(value);
                    queryWrapper.notExists(name, list);
                    break;
                case NE:
                    queryWrapper.ne(name, value);
                    break;
                case NOT_NULL:
                case NNL:
                    queryWrapper.isNotNull(name);
                    break;
                case IS_NULL:
                case INL:
                    queryWrapper.isNull(name);
                    break;
                case BETWEEN:
                case BT:
                    list = objToList(value);
                    verifyBetween(name, list);
                    queryWrapper.between(name, dateConvert(list.get(0), true), dateConvert(list.get(1)));
                    break;
                case NOT_BETWEEN:
                case NBT:
                    list = objToList(value);
                    verifyBetween(name, list);
                    queryWrapper.notBetween(name, dateConvert(list.get(0), true), dateConvert(list.get(1)));
                    break;
                default:
                    break;
            }
            if (isOr != null && isOr) {
                queryWrapper.or();
            }
        }
    }

    /**
     * 日期格式增加时分秒
     *
     * @param time
     * @param isStart
     * @return
     */
    private static Object dateConvert(Object time, boolean isStart) {
        if (time instanceof String) {
            LocalDate date = DateUtil.parse(time.toString(), LocalDate::from);
            return isStart ? date.atStartOfDay() : date.atTime(23, 59, 59);
        }
        return time;
    }

    /**
     * 日期格式增加时分秒
     *
     * @param time
     * @return
     */
    private static Object dateConvert(Object time) {
        return dateConvert(time, false);
    }


    /**
     * Object 转 List
     * 支持数组、集合、字符串分割
     *
     * @param value
     * @return
     */
    private static List objToList(Object value) {
        List between = new ArrayList();
        if (ObjectUtils.isEmpty(value)) {
            return between;
        }
        if (value instanceof List) {
            between = new ArrayList((List) value);
        } else if (value instanceof Set) {
            between = new ArrayList((Set) value);
        } else if (value instanceof Object[]) {
            between = Arrays.asList((Object[]) value);
        } else if (value instanceof String) {
            between = Arrays.asList(((String) value).split(SymbolConstant.COMMA));
        } else {
            between = Arrays.asList(String.valueOf(value).split(SymbolConstant.COMMA));
        }
        return between;
    }

    /**
     * 反射获取属性
     *
     * @param clazz
     * @param fields
     * @return
     */
    private static List<Field> fields(Class clazz, List<Field> fields) {
        if (!Objects.isNull(clazz)) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            fields(clazz.getSuperclass(), fields);
        }
        return fields;
    }

    private static void verifyBetween(String fieldName, List between) {
        Assert.isTrue(between.size() >= 2, "param '" + getOriginalField(fieldName) + "' need two elements");
    }

    /**
     * 获取原字段
     *
     * @return
     */
    private static String getOriginalField(String fieldName) {
        String[] names = fieldName.split("\\" + SymbolConstant.SPOT);
        return StringUtils.underlineToCamel(names[names.length - 1]);
    }

    /**
     * 判断是否包含条件 or
     *
     * @param content 内容
     * @return
     */
    private static List<String> splitOr(String content) {
        List<String> results = new ArrayList<>();
        String[] valuesArray = content.split("\\s+" + "(?i)" + OR + "\\s+");
        if (!ObjectUtils.isEmpty(valuesArray)) {
            results = Arrays.stream(valuesArray).collect(Collectors.toList());
        }
        return results;
    }

    public static Page getPage(PageQO pageQO) {
        return new Page(pageQO.getCurrent(), pageQO.getSize());
    }

}
